﻿using Dreamwork.Core.Infrastructure;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace Dreamwork.Core.Infrastructure.Mongodb
{
	public class MongoRepository<TCollection> : IMongoRepository<TCollection> where TCollection : Collection
	{
		private readonly IMongodbContext _context;
		private readonly ILogger<MongoRepository<TCollection>> _logger;
		private readonly IMongoCollection<TCollection> _collection;
		public MongoRepository(IMongodbContext context, ILogger<MongoRepository<TCollection>> logger)
		{
			_context = context;
			var collection = Activator.CreateInstance<TCollection>();
			_collection = _context.Db.GetCollection<TCollection>($"{collection.GetType().Name}s");
			_logger = logger;
		}

		public async Task AddAsync(TCollection item)
		{
			try
			{
				await _collection.InsertOneAsync(item);
			}
			catch (Exception ex)
			{
				_logger.LogError(ex, ex.Message);
			}
		}

		public async Task<TCollection> GetAsync(Guid id)
		{
			try
			{
				return await _collection
								.Find(user => user.Id == id.ToString())
								.FirstOrDefaultAsync();
			}
			catch (Exception ex)
			{
				_logger.LogError(ex, ex.Message);
				return null;
			}
		}
		public async Task<TCollection> GetAsync(Expression<Func<TCollection, bool>> selector)
		{
			try
			{
				var query = _collection.Find(selector);
				return await query.FirstOrDefaultAsync();
			}
			catch (Exception ex)
			{
				_logger.LogError(ex, ex.Message);
				return null;
			}
		}
		public async Task<IEnumerable<TCollection>> GetListAsync(Expression<Func<TCollection, bool>> selector)
		{
			try
			{
				var query = _collection.Find(selector);
				return await query.ToListAsync();
			}
			catch (Exception ex)
			{
				_logger.LogError(ex, ex.Message);
				return null;
			}
		}
		public async Task<IEnumerable<TCollection>> GetAllAsync()
		{
			try
			{
				return await _collection
						.Find(_ => true).ToListAsync();
			}
			catch (Exception ex)
			{
				_logger.LogError(ex, ex.Message);
				return new List<TCollection>();
			}
		}

		public async Task<bool> RemoveAsync(Guid id)
		{
			try
			{
				DeleteResult actionResult
					= await _collection.DeleteOneAsync(
						Builders<TCollection>.Filter.Eq("Id", id));

				return actionResult.IsAcknowledged
					&& actionResult.DeletedCount > 0;
			}
			catch (Exception ex)
			{
				// log or manage the exception
				_logger.LogError(ex, ex.Message);
				return false;
			}
		}

		public async Task<bool> UpdateAsync(TCollection model)
		{
			var filter = Builders<TCollection>.Filter.Eq(data => data.Id, model.Id);
			var properties = model.GetType().GetProperties();

			var updateBuilder = Builders<TCollection>.Update;
			UpdateDefinition<TCollection> update = null;
			foreach (var property in properties)
			{
				update = updateBuilder.Set(source => Expression.Property(
				  Expression.Constant(source),
				  property.Name).Expression, property.GetValue(model));
			}

			try
			{
				if (update == null)
				{
					_logger.LogWarning("No change.");
					return false;
				}
				UpdateResult actionResult
					= await _collection.UpdateOneAsync(filter, update);
				return actionResult.IsAcknowledged
					&& actionResult.ModifiedCount > 0;
			}
			catch (Exception ex)
			{
				_logger.LogError(ex, ex.Message);
				return false;
			}
		}
		public async Task<bool> RemoveAllAsync()
		{
			try
			{
				DeleteResult actionResult
					= await _collection.DeleteManyAsync(new BsonDocument());

				return actionResult.IsAcknowledged
					&& actionResult.DeletedCount > 0;
			}
			catch (Exception ex)
			{
				_logger.LogError(ex, ex.Message);
				return false;
			}
		}
	}
}
